#include "stm32f10x_gpio.h"
#include "stm32f10x_i2c.h"
#include "stm32f10x_rcc.h"

#include "global_conf.h"
#include "rtc.h"
#include "i2c_hal.h"

static unsigned char bin2bcd(unsigned char bin);
static unsigned char bcd2bin(unsigned char bcd);

static error_callback_t     rtc_callback;
static void i2c_init(void);
static void i2c_start(unsigned char address, enum X_I2C_MODE mode);
static void i2c_stop(unsigned address);
static void i2c_close(void);
static unsigned char i2c_receive_byte_ack(void);
static unsigned char i2c_receive_byte_nack(void);
static unsigned char i2c_send_byte(const unsigned char ucdata);
static unsigned long i2c_read(unsigned char slave_address, unsigned char from_address, unsigned long length, unsigned char *pdata);
static unsigned long i2c_write(unsigned char slave_address, unsigned char from_address, unsigned long length, unsigned char *pdata);

static void i2c_stop(unsigned address)
{
    unsigned long timeout = RTC_RETRY;
    /* Send STOP Condition after last byte has been transmitted */
    I2C_GenerateSTOP(I2C_USED, ENABLE);
    /* wait for EV8_2 --> byte has been transmitted */
    while(!I2C_CheckEvent(I2C_USED, I2C_EVENT_MASTER_BYTE_TRANSMITTED) ) {
        if (!(timeout--)) {
            rtc_callback(RTC_ERROR);
        }
    }
}

unsigned char i2c_receive_byte_ack(void)
{
    unsigned long timeout = RTC_RETRY;
    /* Enable acknowledge of received data */
    I2C_AcknowledgeConfig(I2C_USED, ENABLE);
    /* Wait until one byte has been received */
    while( !I2C_CheckEvent(I2C_USED, I2C_EVENT_MASTER_BYTE_RECEIVED)) {
        if (!(timeout--)) {
            rtc_callback(RTC_ERROR);
        }
    }
    /* Read data from I2C data register and return data byte */
    unsigned char data = (unsigned char) I2C_ReceiveData(I2C_USED);
    return data;
}

static unsigned char i2c_receive_byte_nack(void)
{
     unsigned long timeout = RTC_RETRY;
    /* Disable acknowledge of received data */
    I2C_AcknowledgeConfig(I2C_USED, DISABLE);
    I2C_GenerateSTOP(I2C_USED, ENABLE);
    /* Wait until one byte has been received */
    while( !I2C_CheckEvent(I2C_USED, I2C_EVENT_MASTER_BYTE_RECEIVED) ) {
        if (!(timeout--)) {
            rtc_callback(RTC_ERROR);
        }
    }
    /* Read data from I2C data register and return data byte */
    unsigned char data = (unsigned char) I2C_ReceiveData(I2C_USED);
    return data;
}

static unsigned char i2c_send_byte(const unsigned char ucdata)
{
     unsigned long timeout = RTC_RETRY;
    I2C_AcknowledgeConfig(I2C_USED, ENABLE);
    /* Wait for I2C EV8 --> last byte is still being transmitted (last byte in SR, buffer empty), next byte can already be written */
    while(!I2C_CheckEvent(I2C_USED, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) {
        if (!(timeout--)) {
            rtc_callback(RTC_ERROR);
        }
    }
    I2C_SendData(I2C_USED, ucdata);
    while(!I2C_CheckEvent(I2C_USED, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) {
        if (!(timeout--)) {
            rtc_callback(RTC_ERROR);
        }
    }
    return 1;
}

void i2c_init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    I2C_InitTypeDef I2C_InitStruct;

    RCC_APB1PeriphClockCmd(I2C_CLK, ENABLE);
    RCC_APB2PeriphClockCmd(I2C_GPIO_CLK, ENABLE);

    /*
     * setup SCL and SDA pins
     */
    GPIO_InitStructure.GPIO_Pin = I2C_SCL_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = I2C_SDA_PIN;
    GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStructure);

    /* configure I2C */
    I2C_InitStruct.I2C_ClockSpeed = I2C_CLOCK_HZ;
    I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
    I2C_InitStruct.I2C_OwnAddress1 = 0x00;
    I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; /* Enable ACK as default */
    I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_Init(I2C_USED, &I2C_InitStruct);

    I2C_Cmd(I2C_USED, ENABLE);
}

void i2c_close(void)
{
    I2C_DeInit(I2C_USED);
}

static void i2c_start(unsigned char address, enum X_I2C_MODE mode)
{
    unsigned long timeout = RTC_RETRY;
    /* Wait until I2C is not busy any more */
    while (I2C_GetFlagStatus(I2C_USED, I2C_FLAG_BUSY)) {
        if (!(timeout--)) {
            rtc_callback(RTC_ERROR);
        }
    }

    /* Send START condition */
    I2C_GenerateSTART(I2C_USED, ENABLE);

    /* Wait for I2C1 EV5 --> Slave has acknowledged start condition */
    while (!I2C_CheckEvent(I2C_USED, I2C_EVENT_MASTER_MODE_SELECT)) {
        if (!(timeout--)) {
            rtc_callback(RTC_ERROR);
        }
    }

    /* Send slave Address for write
     * Wait for I2C EV6, check if
     * either Slave has acknowledged Master transmitter or
     * Master receiver mode, depending on the transmission
     * direction
     */
    if (mode == READ) {
        I2C_Send7bitAddress(I2C_USED, address, I2C_Direction_Receiver);
        while(!I2C_CheckEvent(I2C_USED, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) {
            if (!(timeout--)) {
                rtc_callback(RTC_ERROR);
            }
        }
    } else if (mode == WRITE) {
        I2C_Send7bitAddress(I2C_USED, address, I2C_Direction_Transmitter);
        while(!I2C_CheckEvent(I2C_USED, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) {
            if (!(timeout--)) {
                rtc_callback(RTC_ERROR);
            }
        }
    } else {
        /* Call user error handler */
    }
}

unsigned long
i2c_read(unsigned char slave_address, unsigned char from_address, unsigned long length, unsigned char *pdata)
{
    unsigned long count = 0;
    i2c_start(slave_address << 1, WRITE);
    i2c_send_byte(from_address);
    i2c_stop(slave_address);
    if (!length) {
        return 0;
    } else if (length == 1) {
        i2c_start(slave_address << 1, READ);
        *pdata = i2c_receive_byte_nack();
        /* i2c_stop(slave_address); */
        count++;
    } else {
        i2c_start(slave_address << 1, READ);
        for (count = 1; count < length; count++) {
            *pdata++ = i2c_receive_byte_ack();
        }
        /* Last byte */
        *pdata = i2c_receive_byte_nack();
        /* i2c_stop(slave_address); */
    }
    return count;
}

unsigned long
i2c_write(unsigned char slave_address, unsigned char from_address, unsigned long length, unsigned char *pdata)
{
    unsigned long count = 0;
    i2c_start(slave_address << 1, WRITE);
    i2c_send_byte(from_address);
    if (!length) {
        return 0;
    } else {
        for (count = 0; count < length; count++) {
            i2c_send_byte(*pdata++);
        }
    }
    i2c_stop(slave_address);
    return count;
}

static unsigned char bin2bcd(unsigned char bin)
{
    return ((((bin / 10) & 0xF) << 4) | ((bin % 10) & 0x0F));
}
static unsigned char bcd2bin(unsigned char bcd)
{
    return (((bcd >> 4) & 0X0F) * 10 | (bcd & 0x0F));
}

void rtc_setsecond(unsigned char second)
{
    unsigned char buf[1] = {bin2bcd(second)};
    i2c_write(DS1307_ADDR, SEC_REG, 1, buf);
}

void rtc_setminute(unsigned char minute)
{
    unsigned char buf[1] = {bin2bcd(minute)};
    i2c_write(DS1307_ADDR, MIN_REG, 1, buf);
}

void rtc_sethour(unsigned char hour)
{
    unsigned char buf[1] = {bin2bcd(hour)};
    unsigned char r_buf[1];
    i2c_read(DS1307_ADDR, HOUR_REG, 1, r_buf);
    buf[0] |= (r_buf[0] & MODE12_24);
    i2c_write(DS1307_ADDR, HOUR_REG, 1, buf);
}

void rtc_setdate(unsigned char date)
{
    unsigned char buf[1] = {bin2bcd(date)};
    i2c_write(DS1307_ADDR, DATE_REG, 1, buf);
}

void rtc_setday(unsigned char day)
{
    unsigned char buf[1] = {bin2bcd(day)};
    i2c_write(DS1307_ADDR, DAY_REG, 1, buf);
}

void rtc_setmonth(unsigned char month)
{
    unsigned char buf[1] = {bin2bcd(month)};
    i2c_write(DS1307_ADDR, MONTH_REG, 1, buf);
}

void rtc_setyear(unsigned char year)
{
    unsigned char buf[1] = {bin2bcd(year)};
    i2c_write(DS1307_ADDR, YEAR_REG, 1, buf);
}

void rtc_setmode(enum rtc_mode mode)
{
    unsigned char buf[1];
    i2c_read(DS1307_ADDR, HOUR_REG, 1, buf);
    if (mode == MODE24) {
        buf[0] |= MODE12_24;
        i2c_write(DS1307_ADDR, HOUR_REG, 1, buf);
    } else {
        buf[0] &= ~MODE12_24;
        i2c_write(DS1307_ADDR, HOUR_REG, 1, buf);
    }
}

/*============================================================================*/
/* APIs */

void rtc_open(struct xrtc_time *time)
{
    i2c_init();
    rtc_callback = time->rtc_error_handler;
    /* Enable osilator at the first time*/
    rtc_setsecond(0x00);
    rtc_settime(time);
}
unsigned char rtc_gettime(struct xrtc_time *time)
{
    unsigned char buf[TIME_SIZE];
    if ( (i2c_read(DS1307_ADDR, SEC_REG, TIME_SIZE, buf)) != TIME_SIZE ) {
        return 0;
    }
    time->seconds   = bcd2bin(buf[0] & 0x7F);
    time->minutes   = bcd2bin(buf[1] & 0x7F);
    if (buf[2] & MODE12_24) {
        time->mode = MODE24;
    } else {
        time->mode = MODE12;
    }
    if (time->mode == MODE12 ) {
        time->hours = bcd2bin(buf[2] & 0x1F);
        if (buf[2] & 0x20) {
            time->am_pm = 1;
        } else {
            time->am_pm = 0;
        }
    } else {
        time->hours = bcd2bin(buf[2] & 0x3F);
    }
    time->day       = bcd2bin(buf[3] & 0x07);
    time->date      = bcd2bin(buf[4] & 0x3F);
    time->month     = bcd2bin(buf[5] & 0x1F);
    time->year      = time->year_offset + bcd2bin(buf[6]);
    return 1;
}
unsigned char rtc_settime(struct xrtc_time *time)
{
    unsigned char buf[TIME_SIZE];
    buf[0] = bin2bcd(time->seconds);
    buf[1] = bin2bcd(time->minutes);
    if (time->mode == MODE12) {
        if (time->am_pm) {
            buf[2] = bin2bcd(time->hours) | PM;
        } else {
             buf[2] = bin2bcd(time->hours) & (~PM);
        }
        buf[2] = bin2bcd(time->hours) & (~MODE12_24);
    } else {
        buf[2] = bin2bcd(time->hours) | MODE12_24;
    }
    buf[3] = bin2bcd(time->day);
    buf[4] = bin2bcd(time->date);
    buf[5] = bin2bcd(time->month);
    buf[6] = bin2bcd(time->year - time->year_offset);
    if ( (i2c_write(DS1307_ADDR, SEC_REG, TIME_SIZE, buf)) != TIME_SIZE ) {
        return 0;
    } else {
        return 1;
    }
}

void rtc_close(void)
{
    i2c_close();
}